﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using NMF.Utilities;

namespace NMF.Optimizations.Optimizer
{
    public class ImpactSensitiveOptimizer : OptimizerBase, IOptimizer 
    {
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0")]
        protected override IOptimizationScenario FindBestScenario(IOptimizationScenario initialScenario, IScenarioEvaluator evaluator)
        {
            var context = new OptimizationContext(initialScenario.Optimization, evaluator, initialScenario);
            context.ProcessScenario(initialScenario, Enumerable.Empty<IOptimizationModification>());
            return context.BestScenario;
        }

        protected class OptimizationContext
        {
            public OptimizationContext(Optimization optimization, IScenarioEvaluator evaluator, IOptimizationScenario initialBestScenario)
            {
                this.Evaluator = evaluator;
                this.Optimization = optimization;
                this.BestScenario = initialBestScenario;
            }

            public IOptimizationScenario BestScenario { get; private set; }
            public IScenarioEvaluator Evaluator { get; private set; }
            public Optimization Optimization { get; private set; }

            public virtual void ProcessScenario(IOptimizationScenario currentScenario, IEnumerable<IOptimizationModification> forbidden)
            {
                var mods = CreateInitialModificationGroups(forbidden);
                
                if (!Optimization.ForceModifications || mods.Count == 0)
                {
                    TestAndSetScenario(currentScenario);
                }

                ExpandModificationGroups(mods);

                ApplyGroups(currentScenario, forbidden, mods);
            }

            private void TestAndSetScenario(IOptimizationScenario scenario)
            {
                var cost = Evaluator.EvaluateScenario(scenario);
                scenario.Cost = cost;
                if (cost < BestScenario.Cost)
                {
                    BestScenario = scenario;
                }
            }

            protected virtual IList<ModificationGroup> CreateInitialModificationGroups(IEnumerable<IOptimizationModification> forbidden)
            {
                var hashMods = new HashSet<IOptimizationModification>(Optimization.GetCurrentModifications());
                hashMods.ExceptWith(forbidden);

                var mods = new List<ModificationGroup>(hashMods.Count);
                var counter = 0;
                foreach (var item in hashMods)
                {
                    var box = new SingleModification(item);
                    box.Index = counter;
                    mods.Add(box);
                    counter++;
                }
                return mods;
            }

            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "2")]
            protected virtual void ApplyGroups(IOptimizationScenario initial, IEnumerable<IOptimizationModification> forbidden, IList<ModificationGroup> mods)
            {
                var stack = new Stack<IOptimizationModification>();

                bool isEmptyRestriction = forbidden.IsNullOrEmpty();

                foreach (var group in mods)
                {
                    if (group.Ignore) continue;

                    foreach (var mod in group.Modifications)
                    {
                        if (!mod.Apply()) continue;
                        stack.Push(mod);
                    }

                    var scenario = initial.Fork(group.Modifications);
                    var newForbidden = group.Forbidden.SelectMany(g => g.Modifications);

                    ProcessScenario(scenario, isEmptyRestriction ? newForbidden : forbidden.Concat(newForbidden));

                    while (stack.Count > 0) stack.Pop().Reverse();
                }
            }

            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0")]
            protected virtual void ExpandModificationGroups(IList<ModificationGroup> mods)
            {
                var simples = mods.Count;
                for (int i = 1; i < mods.Count; i++)
                {
                    for (int j = 0; j < i && j < simples; j++)
                    {
                        var complex = mods[i];
                        var simple = mods[j] as SingleModification;
                        if (!complex.Impact.IntersectsWith(simple.Impact))
                        {
                            if (!Optimization.ForceModifications)
                            {
                                complex.Forbidden.Add(simple);
                                simple.Forbidden.Add(complex);
                            }
                            else
                            {
                                complex.Ignore = true;
                                simple.Ignore = true;
                            }
                            if (simple.Index < complex.GetIndices().First())
                            {
                                var compound = new CompositeModificationGroup(simple, complex);
                                mods.Add(compound);
                            }
                        }
                    }
                }
            }
        }
    }
}
